package com.yifei.security;

import com.yifei.constants.SecurityConstants;
import com.yifei.exception.filter.CustomAccessDeniedHandler;
import com.yifei.exception.filter.CustomAuthenticationEntryPoint;
import com.yifei.filter.JwtTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * @Description:
 * @author: yiFei
 * @date: 2022/1/4 1:41
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {

    @Autowired
    private JwtTokenFilter jwtTokenFilter;

    @Autowired
    private CustomAccessDeniedHandler accessDeniedHandler;

    @Autowired
    private CustomAuthenticationEntryPoint authenticationEntryPoint;

    /**
     * 授权
     *
     * @param http
     * @return
     * @throws Exception
     */
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // Security 配置
        return http
                // TODO 上线取消 cors 配置 用 Nginx 代理
                .cors(AbstractHttpConfigurer::disable)
                // 关闭 CSRF
                .csrf(AbstractHttpConfigurer::disable)
                // 关闭 退出登录，自定义接口退出
                .logout(AbstractHttpConfigurer::disable)
                // 防止 缓存冲突  ( 禁用缓存控制以便让前端或者后端自己进行缓存控制 )
                .headers(header -> header.cacheControl().disable())
                //  放行可匿名访问的白名单接口
                .authorizeRequests(auth -> auth.antMatchers(SecurityConstants.SECURITY_LOGIN_PATH).permitAll())
                //  需要校验的接口
                .authorizeRequests(auth -> auth.anyRequest().authenticated())
                //  禁用  HttpSession
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                // 添加过滤器 ( jwt 过滤器 )
                .addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class)
                // 配置错误响应
                .exceptionHandling(httpSecurityExceptionHandlingConfigurer ->
                        httpSecurityExceptionHandlingConfigurer
                                .authenticationEntryPoint(authenticationEntryPoint)
                                .accessDeniedHandler(accessDeniedHandler)
                )
                //  构建返回
                .build();
    }

    /**
     * 忽略保护资源
     */
    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        /**
         * Security 6.X 配置
         * return (web) -> web.ignoring()
         *                 .requestMatchers(
         *                         "/ignoring/**"
         *                 );
         */
        return (web) -> web.ignoring()
                .antMatchers(SecurityConstants.WEB_IGNORING_PATH);
    }

    /**
     * 密码编码器
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 无法直接注入 AuthenticationManager
     *
     * @param authenticationConfiguration
     * @return
     * @throws Exception
     */
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

}
